/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package sk.stancel.peter.sudoku.core;

import java.io.Serializable;
import java.util.Random;

/**
 *
 * @author Peter Stancel
 */
public final class Field implements Serializable {

	private static final long serialVersionUID = 2763573275805076138L;
	/** Generated field. */
    private Tile[][] tiles = new Tile[9][9];
    /** Playing field tiles. */
    private final Tile[][] playTiles = new Tile[9][9];
    /** Temp field tiles. */
    private final Tile[][] tempTiles = new Tile[9][9];
    /** Game state. */
    private GameState state = GameState.PLAYING;
    /** When it has true value, it ends generating sudoku. */
    private boolean endOfSudoku = false;

    /**
     * Constructor.
     */
    public Field() {
        sudokuGenerate();
        copyTiles(tempTiles, tiles);
    }

    /**
     * Constructor.
     * @param ints ints representyng numbers in field
     */
    public Field(int[] ints) {
        makeCustomSudoku(ints);
        copyTiles(tempTiles, playTiles);
    }

    /**
     * Method which generates Sudoku.
     */
    private void sudokuGenerate() {
        do {
            generateNulls();
            generate(0, 0);
            endOfSudoku = false;
            generatePlayTiles();
            do {
                copyTiles(tempTiles, playTiles);
                solver1to9(0, 0);
                endOfSudoku = false;
            } while (trySudoku());
            do {
                copyTiles(tempTiles, playTiles);
                solver9to1(0, 0);
                endOfSudoku = false;
            } while (trySudoku());
        } while (nullCounter());
    }

    /**
     * Generates nulls in fields.
     */
    private void generateNulls() {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                tiles[i][j] = new Tile(0, false);
                playTiles[i][j] = new Tile(0, true);
                tempTiles[i][j] = new Tile(0, false);
            }
        }
    }

    /**
     * Generates random sudoku.
     * @param row row number
     * @param column row number
     */
    private void generate(int row, int column) {
        for (int i = 0; i < 9; i++) {
            int number = tiles[row][column].getRandomNumber();

            if (checkRow(row, column, number, tiles) && checkColumn(row, column, number, tiles) && checkBox(row, column, number, tiles)) {
                tiles[row][column].setValue(number);
                next(row, column, 1);
            }
        }
        if (!endOfSudoku) {
            tiles[row][column].setValue(0);
        }
    }

    /**
     * Shifts row or column about 1 position.
     * @param row row number
     * @param column column number
     */
    private void next(int row, int column, int number) {
        if (row != 8 || column != 8) {
            if (column == 8) {
                if (number == 1) {
                    generate(row + 1, 0);
                } else if (number == 2) {
                    solver1to9(row + 1, 0);
                } else {
                    solver9to1(row + 1, 0);
                }
            } else {
                if (number == 1) {
                    generate(row, column + 1);
                } else if (number == 2) {
                    solver1to9(row, column + 1);
                } else {
                    solver9to1(row, column + 1);
                }
            }
        } else {
            endOfSudoku = true;
        }
    }

    /**
     * Generates playing field. From tiles are random chosen <i>k<i> positions in all boxes.
     */
    private void generatePlayTiles() {
        Random rnd = new Random();
        int rrow;
        int rcolumn;
        for (int k = 0; k < 4; k++) {
            for (int row = 0; row < 9; row = row + 3) {
                for (int column = 0; column < 9; column = column + 3) {
                    rrow = rnd.nextInt(3);
                    rcolumn = rnd.nextInt(3);

                    playTiles[rrow + row][rcolumn + column].setValue(tiles[rrow + row][rcolumn + column].getValue());
                    playTiles[rrow + row][rcolumn + column].setChange(false);
                }
            }
        }
    }

    /**
     * Copies second field into first field.
     * @param tiles1 first field
     * @param tiles2 second field
     */
    public void copyTiles(Tile[][] tiles1, Tile[][] tiles2) {
        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                tiles1[row][column].setValue(tiles2[row][column].getValue());
            }
        }
    }

    /**
     * Solves sudoku. It try all numbers from 1 to 9.
     * @param row row number
     * @param column column number
     */
    private void solver1to9(int row, int column) {
        if (tempTiles[row][column].getValue() != 0) {
            next(row, column, 2);
        } else {
            for (int number = 1; number < 10; number++) {
                if (checkRow(row, column, number, tempTiles) && checkColumn(row, column, number, tempTiles) && checkBox(row, column, number, tempTiles)) {
                    tempTiles[row][column].setValue(number);
                    next(row, column, 2);
                }
            }
            if (!endOfSudoku) {
                tempTiles[row][column].setValue(0);
            }
        }
    }

    /**
     * Solves sudoku. It try all numbers from 9 to 1.
     * @param row row number
     * @param column column number
     */
    private void solver9to1(int row, int column) {
        if (tempTiles[row][column].getValue() != 0) {
            next(row, column, 3);
        } else {
            for (int number = 9; number > 0; number--) {
                if (checkRow(row, column, number, tempTiles) && checkColumn(row, column, number, tempTiles) && checkBox(row, column, number, tempTiles)) {
                    tempTiles[row][column].setValue(number);
                    next(row, column, 3);
                }
            }
            if (!endOfSudoku) {
                tempTiles[row][column].setValue(0);
            }
        }
    }

    /**
     * Compares tempTiles and tiles. If they have different values,
     * value from tiles with same position is copyred to playTiles.
     * @return false if values in tempTiles have the same values as tiles
     */
    private boolean trySudoku() {
        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                if (tempTiles[row][column].getValue() != tiles[row][column].getValue()) {
                    playTiles[row][column].setValue(tiles[row][column].getValue());
                    playTiles[row][column].setChange(false);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Counts nulls in playTiles. If are nulls lower than 50, starts generate Sudoku.
     */
    private boolean nullCounter() {
        int count = 0;
        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                if (playTiles[row][column].getValue() == 0) {
                    count++;
                }
            }
        }
        if (count < 48) {
            return true;
        }
        return false;
    }

    /**
     * Checks if number is only one in a row.
     * @param row row number
     * @param number number number
     * @param tile tile Tile[][]
     * @return true or false
     */
    private boolean checkRow(int row, int column, int number, Tile[][] tile) {
        for (int col = 0; col < 9; col++) {
            if (col != column) {
                if (tile[row][col].getValue() == number) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Checks if number is only one in a column.
     * @param column column number
     * @param number number number
     * @param tile tile Tile[][]
     * @return true or false
     */
    private boolean checkColumn(int row, int column, int number, Tile[][] tile) {
        for (int rrow = 0; rrow < 9; rrow++) {
            if (row != rrow) {
                if (tile[rrow][column].getValue() == number) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Checks if number is only one in a box.
     * @param row row number
     * @param column column number
     * @param number number number
     * @param tile tile Tile[][]
     * @return true or false
     */
    private boolean checkBox(int row, int column, int number, Tile[][] tile) {
        int rrow = row;
        int ccolumn = column;

        row = (row / 3) * 3;
        column = (column / 3) * 3;

        for (int r = 0; r < 3; r++) {
            for (int c = 0; c < 3; c++) {
                if ((column + c) != ccolumn && (row + r) != rrow) {
                    if (tile[row + r][column + c].getValue() == number) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Tries if is sudoku solved correctly.
     * @return true or false
     */
    private boolean isSolved() {
        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                if (playTiles[row][column].getValue() == 0) {
                    return false;
                }
            }
        }
        return isPlayable(playTiles);
    }

    /**
     * Checks for two same numbers in rows,columns and boxes.
     * @return true or false
     */
    private boolean isPlayable(Tile[][] tiles1) {
        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                if (tiles1[row][column].getValue() != 0) {
                    if (!checkRow(row, column, tiles1[row][column].getValue(), tiles1) || !checkColumn(row, column, tiles1[row][column].getValue(), tiles1) || !checkBox(row, column, tiles1[row][column].getValue(), tiles1)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Sets number into a tile. It checks if is end of game.
     * @param row row number
     * @param column column number
     * @param number number number
     */
    public void setPlayTile(int row, int column, int number) {
        playTiles[row][column].setValue(number);
        if (isSolved()) {
            setState(GameState.SOLVED);
        }
    }

    /**
     * Returns the state.
     * @return the state
     */
    public GameState getState() {
        return state;
    }

    /**
     * Sets the state.
     * @param state the state to set
     */
    public void setState(GameState state) {
        this.state = state;
    }

    /**
     * Returns the playTile.
     * @param row row number
     * @param column column number
     * @return the tile
     */
    public Tile getPlayTile(int row, int column) {
        final Tile playTile = playTiles[row][column];
        return playTile;
    }

    /**
     * Returns value of tempTile.
     * @param row row number
     * @param column column number
     * @return the tile
     */
    public int getTempTileValue(int row, int column) {
        return tempTiles[row][column].getValue();
    }

    /**
     * Generates custom sudoku.
     */
    private void makeCustomSudoku(int[] ints) {
        generateNulls();
        int number = 0;
        for (int row = 0; row < 9; row++) {
            for (int column = 0; column < 9; column++) {
                playTiles[row][column].setValue(ints[number]);
                if (ints[number] != 0) {
                    playTiles[row][column].setChange(false);
                }
                number++;
            }
        }
    }

    /**
     * Solves sudoku.
     * @return true or false
     */
    public boolean solveIt() {
        copyTiles(playTiles, tempTiles);
        return isSolved();
    }

    /**
     * Returns true if Sudoku has only one solution.
     * @return true if Sudoku has only one solution
     */
    public boolean checkIt() {
        if (isPlayable(tempTiles)) {
            solver1to9(0, 0);
            endOfSudoku = false;
//            copyTiles(tiles, tempTiles);
//            resetTempTiles();
            return true;
        }
        return false;
    }
    
    public void solveMedium(){
    	copyTiles(playTiles, tiles);
    	copyTiles(tempTiles, tiles);
    }

//    /**
//     * Resets temp tiles.
//     */
//    public void resetTempTiles() {
//        for (int row = 0; row < 9; row++) {
//            for (int column = 0; column < 9; column++) {
//                if (playTiles[row][column].isChange()) {
//                    tempTiles[row][column].setValue(0);
//                }
//            }
//        }
//    }

//    /**
//     * Compares tempTiles and tiles. 
//     * @return true if values in tempTiles have the same values as tiles
//     */
//    private boolean checkSudoku() {
//        for (int row = 0; row < 9; row++) {
//            for (int column = 0; column < 9; column++) {
//                if (tempTiles[row][column].getValue() != tiles[row][column].getValue()) {
//                    return false;
//                }
//            }
//        }
//        return true;
//    }

    /**
     * Returns true if Sudoku is playable.
     * @return true if Sudoku is playable
     */
    public boolean playableCheck() {
        return isPlayable(tempTiles);
    }
}
